查看原文
其他

在 Substrate 中为你的 runtime 添加合约模块

PolkaWorld 2020-11-10

添加 Pallet(模块)

Substrate 节点模板[1] 提供了最小的 runtime,你可以用它来快速构建自定义区块链。然而,为保持最小限度,它并不包括 FRAME[2] 中的大部分 Pallet(模块)。

本指南将向你展示如何将  Contracts pallet[3] 添加到 runtime 中,以便让你的区块链支持 Wasm 智能合约。你可以按照类似的模式将其他 FRAME pallet 添加到你的 runtime,但需要注意的是,每个 pallet 在使用它时,所需的特定配置设置方面略有不同。

Install the Node Template

当你学习完教程 创建你的第一条 Substrate 链[4],你应该已经在计算机上编译了Substrate Node Template[5]v2.0.0-alpha.8 版本, 如果还没有的话,请先完成教程。

如果你是有经验的开发人员,倾向于选择跳过该教程,建议你可以根据自述文件中的说明安装节点模板。

文档结构

现在,我们将修改 substrate-node-template 让它可以包含 contracts pallet。

选一个你喜欢的代码编辑器打开 substrate-node-template 。我们将编辑两个文件:runtime/src/lib.rs, and runtime/Cargo.toml

substrate-node-template
|
+-- runtime
| |
| +-- Cargo.toml <-- One change in this file
| |
| +-- build.rs
| |
| +-- src
| |
| +-- lib.rs <-- Most changes in this file
|
+-- pallets
|
+-- scripts
|
+-- node
|
+-- ...

导入 Pallet Crate

添加合约模块所需要做的第一件事是在 runtime 的 Cargo.toml 文件中导入 pallet-contracts crate。如果您想在 Cargo References 中找到合适的入门资料,则应查阅其正式文档[6]

打开 substrate-node-template/runtime/Cargo.toml ,你会看到运行时具有的所有依赖关系的列表。例如,它依赖 Balances pallet[7]:

runtime/Cargo.toml

[dependencies.balances]
git = 'https://github.com/paritytech/substrate.git'
default-features = false
package = 'pallet-balances'
tag = 'v2.0.0-alpha.8'

Crate 功能

导入模块 Crate 时需要注意的一件事是,确保正确设置 Crate features 。在上面的代码片段中,你会注意到我们设置了 default_features = false。如果您更仔细地浏览 Cargo.toml 文件,则会发现类似以下内容:

runtime/Cargo.toml

[features]
default = ['std']
std = [
'codec/std',
'client/std',
'sp-std/std',
'sp-io/std',
'balances/std',
'frame-support/std',
#--snip--
]

第二行将 runtime crate 的default 功能定义为 std。你可以想象,每个 pallet crate 都有类似的配置,定义了这个 crate 的默认功能。你的功能将确定应在下游依赖项上使用的功能。例如,上面的代码段应读取为:

此 Substrate runtime 的默认功能是 std。如果为 runtime 启用了 std 功能,那么parity-scale-codecprimitivesclient,和所有其他列出的依赖项也应使用其 std 功能。

这对于使 Substrate 运行时能够编译为本机二进制文件(支持 Rust`std`[8]),和 Wasm 二进制文件(不支持 `no_std`[9])。

要查看这些功能实际上如何在运行时代码中使用,我们可以打开项目文件:

runtime/src/lib.rs

//! The Substrate Node Template runtime. This can be compiled with `#[no_std]`, ready for Wasm.

#![cfg_attr(not(feature = "std"), no_std)]
// `construct_runtime!` does a lot of recursion and requires us to increase the limit to 256.
#![recursion_limit="256"]

// Make the WASM binary available.
#[cfg(feature = "std")]
include!(concat!(env!("OUT_DIR"), "/wasm_binary.rs"));

// --snip--

可以看到,在文件顶部,我们定义了在使用 std 功能时将使用 no_std

在下面几行中你会看见 #[cfg(feature = "std")] ,在 wasm_binary.rs import 之上,这是一个标志,要求仅在启用 std 功能后才导入 WASM 二进制文件,这时会看到下面的几行。

导入 Contracts Pallet Crate

好的,既然我们已经了解了 crate 功能的基础知识,那么我们实际上可以导入 Contracts pallet 了。Contracts pallet 可能是 FRAME 中最复杂的模块,因此它是添加其他模块时可能涉及的一些棘手问题的一个很好的例子。

首先,我们将通过简单地复制现有模块并更改值来添加新的依赖项。因此,根据上面显示的 balances 导入,contracts 导入将类似于:

runtime/Cargo.toml

[dependencies.contracts]
git = 'https://github.com/paritytech/substrate.git'
default-features = false
package = 'pallet-contracts'
tag = 'v2.0.0-alpha.8'

[dependencies.contracts-primitives]
git = 'https://github.com/paritytech/substrate.git'
default-features = false
package = 'pallet-contracts-primitives'
tag = 'v2.0.0-alpha.8'

与其他模块一样,Contracts pallet 具有 std 功能。当运行时使用其自己的 std 功能构建时,我们应该构建其 std 功能。将以下两行添加到运行时的 std 功能。

runtime/Cargo.toml

[features]
default = ["std"]
std = [
#--snip--
'contracts/std',
'contracts-primitives/std',
#--snip--
]

如果您忘记提前设置功能,则在构建到本机二进制文件时会出现类似以下错误:

error[E0425]: cannot find function `memory_teardown` in module `sandbox`
  --> ~/.cargo/git/checkouts/substrate-7e08433d4c370a21/83a6f1a/primitives/sandbox/src/../without_std.rs:53:12
   |
53 |         sandbox::memory_teardown(self.memory_idx);
   |                  ^^^^^^^^^^^^^^^ not found in `sandbox`

error[E0425]: cannot find function `memory_new` in module `sandbox`
  --> ~/.cargo/git/checkouts/substrate-7e08433d4c370a21/83a6f1a/primitives/sandbox/src/../without_std.rs:72:18
   |
72 |         match sandbox::memory_new(initial, maximum) {
   |

...

现在是时候检查一下是否可以正确编译所有内容:

cargo check

添加 Contracts Pallet

现在,我们已经成功导入了 Contracts pallet crate,我们需要将其添加到运行时中。不同的 pallets(模块)将要求你use不同的对应物。对于 Contracts pallet,我们将使用 Schedule 类型。在运行开始时,将此行与其他 pub use 语句一起添加。

runtime/src/lib.rs

/*** Add This Line ***/
/// Importing the contracts Schedule type.
pub use contracts::Schedule as ContractsSchedule;

实施 Contract Trait

每个 pallet(模块)都有运行时必须实现的称为 Trait 的配置特征。

要弄清楚我们需要为该 pallet(模块)具体实现什么,可以查看 FRAME `contracts::Trait` 文档[10]。对于我们的 runtime,实现将如下所示:

runtime/src/lib.rs

// These time units are defined in number of blocks.
   /* --snip-- */

/*** Add This Block ***/
// Contracts price units.
pub const MILLICENTS: Balance = 1_000_000_000;
pub const CENTS: Balance = 1_000 * MILLICENTS;
pub const DOLLARS: Balance = 100 * CENTS;
/*** End Added Block ***/
impl timestamp::Trait for Runtime {
    /* --snip-- */
}

/*** Add This Block ***/
parameter_types! {
 pub const TombstoneDeposit: Balance = 1 * DOLLARS;
 pub const RentByteFee: Balance = 1 * DOLLARS;
 pub const RentDepositOffset: Balance = 1000 * DOLLARS;
 pub const SurchargeReward: Balance = 150 * DOLLARS;
}

impl contracts::Trait for Runtime {
 type Time = Timestamp;
 type Randomness = RandomnessCollectiveFlip;
 type Call = Call;
 type Event = Event;
 type DetermineContractAddress = contracts::SimpleAddressDeterminer<Runtime>;
 type TrieIdGenerator = contracts::TrieIdFromParentCounter<Runtime>;
 type RentPayment = ();
 type SignedClaimHandicap = contracts::DefaultSignedClaimHandicap;
 type TombstoneDeposit = TombstoneDeposit;
 type StorageSizeOffset = contracts::DefaultStorageSizeOffset;
 type RentByteFee = RentByteFee;
 type RentDepositOffset = RentDepositOffset;
 type SurchargeReward = SurchargeReward;
 type MaxDepth = contracts::DefaultMaxDepth;
 type MaxValueSize = contracts::DefaultMaxValueSize;
}
/*** End Added Block ***/

我们将以 type DetermineContractAddress 类型为例进行更详细的介绍 —— 你可以从 `DetermineContractAddress` 文档[11] 中看到它需要 trait ContractAddressFor。Contracts pallet 本身在 contract::SimpleAddressDeterminator 中实现了具有此特征的类型,因此我们可以使用该实现来满足我们的 contracts::Trait。此时,我真的建议你不管是过程中出现问题或者想加深了解,都可以浏览下 Contracts pallet[12] 的源代码。

将合约添加到 construct_runtime! Macro

接下来,我们需要将 pallet(模块)添加到 construct_runtime! 宏。. 为此,我们需要确定 pallet(模块)公开的类型,以便我们可以告诉运行时它们存在。可能的类型的完整列表可以在 `construct_runtime!` macro documentation[13]中找到。

如果我们仔细查看 Contracts pallet,我们知道它具有:

  • Module Storage: Because it uses the decl_storage! macro.
  • Module Events: Because it uses the decl_event! macro.
  • Callable Functions: Because it has dispatchable functions in the decl_module! macro.
  • Configuration Values: Because the decl_storage! macro has config() parameters.
  • The Module type from the decl_module! macro.

因此,当我们添加 pallet(模块)时,它将如下所示:

runtime/src/lib.rs

construct_runtime!(
    pub enum Runtime where
        Block = Block,
        NodeBlock = opaque::Block,
        UncheckedExtrinsic = UncheckedExtrinsic
    {
        /* --snip-- */

        /*** Add This Line ***/
        Contracts: contracts::{Module, Call, Config, Storage, Event<T>},
    }
);

请注意,并非所有的 pallet(模块)都会公开所有这些运行时类型,而有些可能会公开更多!你应该总是查看 pallet(模块)的源代码或 pallet(模块)的文档,以确定你需要公开其中哪种类型。

这是检查到目前为止运行时是否正确编译的另一个好时机。尽管运行时应进行编译,但整个节点还尚未进行编译。因此,我们仅使用此命令检查 runtime。

cargo check -p node-template-runtime

公开 Contracts API

某些 pallet(包括合同 Contracts pallet)公开了自定义 runtime API 和 RPC 端点。对于 Contracts pallet,这使得可以从链外读取合约状态。

不需要启用 Contracts pallet 上的 RPC 调用即可在我们的链中使用它。但是,我们将执行此操作以在不进行交易的情况下调用节点的存储。

我们首先在 Cargo.toml 中添加所需的 API 依赖项。

runtime/Cargo.toml

[dependencies.contracts-rpc-runtime-api]
git = 'https://github.com/paritytech/substrate.git'
default-features = false
package = 'pallet-contracts-rpc-runtime-api'
version = '0.8.0-alpha.8'
tag = 'v2.0.0-alpha.8'

runtime/Cargo.toml

[features]
default = ["std"]
std = [
#--snip--
'contracts-rpc-runtime-api/std',
]

要获取合同变量的状态,我们必须调用 getter 函数,该函数将返回具有执行状态的 ContractExecResult 包装器。

我们需要将返回类型添加到运行时。将此添加到其他 use 语句中。

runtime/src/lib.rs

/*** Add This Line ***/
use contracts_rpc_runtime_api::ContractExecResult;
/* --snip-- */

现在,我们准备实现 contracts runtime API。在 runtime 快结束的时候,这发生在 impl_runtime_apis! 宏中。

impl_runtime_apis! {
   /* --snip-- */

   /*** Add This Block ***/
    impl contracts_rpc_runtime_api::ContractsApi<Block, AccountId, Balance, BlockNumber>
  for Runtime
 {
  fn call(
   origin: AccountId,
   dest: AccountId,
   value: Balance,
   gas_limit: u64,
   input_data: Vec<u8>,
  ) -> ContractExecResult {
   let exec_result =
    Contracts::bare_call(origin, dest.into(), value, gas_limit, input_data);
   match exec_result {
    Ok(v) => ContractExecResult::Success {
     status: v.status,
     data: v.data,
    },
    Err(_) => ContractExecResult::Error,
   }
  }

  fn get_storage(
   address: AccountId,
   key: [u832],
  ) -> contracts_primitives::GetStorageResult {
   Contracts::get_storage(address, key)
  }

  fn rent_projection(
   address: AccountId,
  ) -> contracts_primitives::RentProjectionResult<BlockNumber> {
   Contracts::rent_projection(address)
  }
 }
   /*** End Added Block ***/
}

这是检查到目前为止运行时是否正确编译的另一个好时机。

cargo check -p node-template-runtime

更新外部节点

至此,我们已经完成了向 runtime 添加 pallet 的工作。现在,我们将注意力转向外部节点,该外部节点通常需要一些相应的更新。对于 Contracts pallet,我们将添加自定义 RPC 端点和创始配置。

添加 RPC 端点

With the proper runtime API exposed. We now add the RPC to the node's service to call into that runtime API. Because we are now working in the outer node, we are not building to no_std and we don't have to maintain a dedicated std feature.

公开了适当的 runtime API 后。现在,我们将 RPC 添加到节点的服务中,以调用该 runtime API。因为我们现在在外部节点上工作,所以我们没有在 no_std 上构建,也不必维护专用的 std 功能。

node/Cargo.toml

[dependencies]
#--snip--
jsonrpc-core = '14.0.5'

[dependencies.pallet-contracts-rpc]
git = 'https://github.com/paritytech/substrate.git'
version = '0.8.0-alpha.8'
tag = 'v2.0.0-alpha.8'

[dependencies.sc-rpc]
git = 'https://github.com/paritytech/substrate.git'
tag = 'v2.0.0-alpha.8'

node/src/service.rs

macro_rules! new_full_start {
 ($config:expr) => {{
        /*** Add This Line ***/
        use jsonrpc_core::IoHandler;

Substrate 提供了一个与我们的节点进行交互的 RPC。但是,默认情况下,它不包含对 contracts pallet 的访问。要与该 pallet(模块)交互,我们必须扩展现有的 RPC 并添加 contracts pallet 及其 API。


/* --snip-- */
Ok(import_queue)
})? // <- Remove semi-colon
/*** Add This Block ***/
.with_rpc_extensions(|builder| -> Result<IoHandler<sc_rpc::Metadata>, _> {
let handler = pallet_contracts_rpc::Contracts::new(builder.client().clone());
let delegate = pallet_contracts_rpc::ContractsApi::to_delegate(handler);

let mut io = IoHandler::default();
io.extend_with(delegate);
Ok(io)
})?;
/*** End Added Block ***/
(builder, import_setup, inherent_data_providers)
}}

初始配置

并不是所有的 pallet 都会有初始配置,但是如果你的 pallet 有的话,你可以从它的文档中进行学习,例如 `pallet_contracts::GenesisConfig` 文档[14] 就描述了在 Contracts pallet 中你需要描述的所有领域。

初始配置由 node/src/chain_spec.rs 控制。我们需要修改这个文件,来包含 ContractsConfig 类型,和顶部的合约价格单位。

node/src/chain_spec.rs

use node_template_runtime::{ContractsConfig, ContractsSchedule};

然后在 testnet_genesis 函数内部,我们需要将合同配置添加到返回的 GenesisConfig 对象中,如下所示:

重要说明:我们从函数参数中获取值 _enable_println。确保删除参数定义之前的下划线。

fn testnet_genesis(initial_authorities: Vec<(AuraId, GrandpaId)>,
    root_key: AccountId,
    endowed_accounts: Vec<AccountId>,
    _enable_println: bool) -> GenesisConfig {

    GenesisConfig {
        /* --snip-- */

        /*** Add This Block ***/
        contracts: Some(ContractsConfig {
            current_schedule: ContractsSchedule {
                    enable_println,
                    ..Default::default()
            },
        }),
        /*** End Added Block ***/
    }
}

运行升级后的链

现在,您可以编译并运行具有合约功能的节点了。使用发布模式编译节点

cargo build --release

在运行链之前,我们首先需要清除链以删除旧的  runtime 逻辑,并为 Contracts pallet 初始化配置。可以在不清除链条的情况下升级链,但这不在本教程的范围之内。

./target/release/node-template purge-chain --dev
./target/release/node-template --dev

添加其他的 FRAME pallets

在本指南中,我们专门介绍了如何导入 Contracts pallet,但是,如本指南开头所述,每个 pallet(模块)都会有所不同。不用担心,你可以始终参考示范性 Substrate 节点 runtime[15],该 runtime 几乎包括 FRAME 中的每个 pallet(模块)。

在 Substrate 节点运行时的 Cargo.toml 文件中,你将看到一个如何导入每个不同 pallet(模块)的示例,在 lib.rs 文件中,你将找到如何将每个 pallet(模块)添加到你的 runtime。你基本上可以将在里面所做的复制到自己的 runtime。

了解更多

  • 在自己的程序包中编写 runtime 的极简教程[16].
  • 现在,你的节点可以运行智能合约了,请继续学习 Substrate ink! smart contracts[17].
  • Substrate Recipes[18] 提供了有关编写 Runtime APIs[19]Custom RPCs[20] 的详细教程,例如本教程中探讨的内容。
  • 了解 Chain Spec[21] 文件以自定义您的 Genesis 配置。

参考文献

  • FRAME `Contracts` Pallet API[22]

参考链接

[1]

Substrate 节点模板: https://github.com/substrate-developer-hub/substrate-node-template

[2]

FRAME: https://www.substrate.io/kb/runtime/frame

[3]

Contracts pallet: https://docs.rs/crate/pallet-contracts/2.0.0-alpha.8

[4]

创建你的第一条 Substrate 链: https://www.substrate.io/tutorials/create-your-first-substrate-chain/v2.0.0-alpha.8

[5]

Substrate Node Template: https://github.com/substrate-developer-hub/substrate-node-template

[6]

正式文档: https://doc.rust-lang.org/cargo/reference/index.html

[7]

Balances pallet: https://docs.rs/crate/pallet-balances/2.0.0-alpha.8

[8]

std: https://doc.rust-lang.org/std/

[9]

no_std: https://rust-embedded.github.io/book/intro/no-std.html

[10]

contracts::Trait 文档: https://docs.rs/pallet-contracts/2.0.0-alpha.8/pallet_contracts/trait.Trait.html

[11]

DetermineContractAddress 文档: https://docs.rs/pallet-contracts/2.0.0-alpha.8/pallet_contracts/trait.Trait.html#associatedtype.DetermineContractAddress

[12]

Contracts pallet: https://github.com/paritytech/substrate/blob/v2.0.0-alpha.8/frame/contracts/src/lib.rs

[13]

construct_runtime! macro documentation: https://docs.rs/frame-support/2.0.0-alpha.8/frame_support/macro.construct_runtime.html

[14]

pallet_contracts::GenesisConfig 文档: https://docs.rs/pallet-contracts/2.0.0-alpha.8/pallet_contracts/struct.GenesisConfig.html

[15]

示范性 Substrate 节点 runtime: https://github.com/paritytech/substrate/blob/v2.0.0-alpha.8/bin/node/runtime/

[16]

在自己的程序包中编写 runtime 的极简教程: https://www.substrate.io/tutorials/pallet-in-own-crate/v2.0.0-alpha.8

[17]

Substrate ink! smart contracts: https://www.substrate.io/kb/smart-contracts

[18]

Substrate Recipes: https://substrate.dev/recipes/

[19]

Runtime APIs: https://substrate.dev/recipes/3-entrees/runtime-api.html

[20]

Custom RPCs: https://substrate.dev/recipes/3-entrees/custom-rpc.html

[21]

Chain Spec: https://www.substrate.io/kb/integrate/chain-spec

[22]

FRAME Contracts Pallet API: https://docs.rs/pallet-contracts/2.0.0-alpha.8/pallet_contracts/index.html



  • 欢迎学习 Substrate: 

    https://substrate.dev/

  • 关注 Substrate 进展:

    https://github.com/paritytech/substrate

  • 关注 Polkadot 进展:

    https://github.com/paritytech/polkadot

  • 申请 Bootcamp: 

    https://bootcamp.web3.foundation/

更多内容:


视频|KSM 最新映射教程

视频教程|如何在 Kusama 成为提名人

视频|Polkadot 治理模式演示


扫码关注公众号,回复 “1” 加入波卡群

关注 PolkaWorld

发现 Web 3.0 时代新机遇


点个 “在看” 再走吧!



    您可能也对以下帖子感兴趣

    文章有问题?点此查看未经处理的缓存